%%%-------------------------------------------------------------------
%%% File        : smtpd.erl
%%% Author      : Fyodor Ustinov <ufm@ufm.su>
%%% Description : SMTP Daemon
%%%
%%% STANDARDS
%%%	RFC 821 (SMTP protocol)
%%%	RFC 1123 (Host requirements)
%%%	RFC 1652 (8bit-MIME transport)
%%%	RFC 1869 (SMTP service extensions)
%%%	RFC 1870 (Message Size Declaration)
%%%	RFC 1985 (ETRN command)
%%%	RFC 2034 (SMTP Enhanced Error Codes)
%%%	RFC 2554 (AUTH command)
%%%	RFC 2821 (SMTP protocol)
%%%	RFC 2920 (SMTP Pipelining)
%%%	RFC 3207 (STARTTLS command)
%%%	RFC 3461 (SMTP DSN Extension)
%%%	RFC 3463 (Enhanced Status Codes)
%%%	RFC 3848 (ESMTP Transmission Types)
%%%	RFC 4954 (AUTH command)
%%%
%%% Created     : 23 Aug 2009 by Fyodor Ustinov <ufm@ufm.su>
%%%-------------------------------------------------------------------
-module(smtpd).
-define(SERVER,smtpd).

-behaviour(gen_server).

%% API
-export([start_link/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

-include("mailer.hrl").

-define(config_get(X, Y), config:get(smtpd, X, Y)).

-record(state, {port = 1025,       % Listening port
		maxconn = 5,       % Max connections
		curconn = 0,       % Current count connections
		ip  = {127,0,0,1}, % binding address
		lsock,             % Listen socket
		ref}).             % Async reference

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
    State = #state{ip = ?config_get(bind_address, {127, 0, 0, 1}), 
		   port = ?config_get(smtp_port, 25),
		   maxconn = ?config_get(smtp_max_servers, 1)
		  },
    process_flag(trap_exit, true),
    {Ra, Rb, Rc} = now(),
    random:seed(Ra, Rb, Rc),
    Opts =[binary, {ip, State#state.ip}, {active, false},
	   {packet, line},{recbuf, 1024},{reuseaddr, true}],
    try 
	{ok, LSock} = gen_tcp:listen(State#state.port, Opts),
	{ok, Ref} = prim_inet:async_accept(LSock, -1),
	{ok, State#state{lsock = LSock, ref = Ref}}
    catch
	_:_ -> {stop, "SMTPD initialisation error."}
    end
 .



%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
    {noreply, State}.


%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------


%% {inet_async, LSock, Ref, {ok, CSock}} : Async connect to socket.
%%     LSock - listen socket
%%     Ref   - reference
%%     CSock - connected socket


%% Handle too many connect attemptions.
handle_info({inet_async, LSock, Ref, {ok, CSock}},
	    #state{lsock = LSock, ref = Ref} = State) when State#state.curconn + 1 > State#state.maxconn ->

    prim_inet:send(CSock,["421 Too many connections\r\n"]),
    gen_tcp:close(CSock),
    {ok, NewRef} = prim_inet:async_accept(LSock, -1),
    {noreply,State#state{ref = NewRef}};

%% New connection
handle_info({inet_async, LSock, Ref, {ok, CSock}},
	    #state{lsock = LSock, ref = Ref} = State) ->

    {ok, Pid} = smtpdc:start_link(CSock),
    gen_tcp:controlling_process(CSock, Pid),
    gen_server:call(Pid, enable_flow),
    {ok, NewRef} = prim_inet:async_accept(LSock, -1),
    {noreply, State#state{ref = NewRef,curconn = State#state.curconn + 1}};

%% Connection closed
handle_info({'EXIT',_Pid,_Status},State) ->
    io:format("SMTPC: ~p dead.~n",[_Pid]),
    {noreply, State#state{curconn = State#state.curconn - 1}};


%% Trash.
handle_info(_Info, State) ->

    case _Info of
	die ->
	    {stop,normal,State};
	All ->
	    io:format("Ho-ho-ho! Info '~p' and state '~p'~n",[All,State]),
	    {noreply,State}
    end.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    io:format("SMTPD: Terminate '~p', '~p'~n",[_Reason, _State]),
    ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
